use std::{
    io::{Error, ErrorKind},
    net::{TcpListener, TcpStream},
    sync::{
        atomic::{AtomicBool, Ordering},
        mpsc, Arc, Mutex,
    },
    thread,
    time::Duration,
};

pub struct Server {
    name: String,
    addr: String,
    stop_flag: Arc<AtomicBool>,
    handler: Option<thread::JoinHandle<()>>,
    local_addr: Option<String>,
}

impl Server {
    pub fn new(addr: &str, server_name: &str) -> Server {
        Server {
            name: server_name.to_string(),
            addr: addr.to_string(),
            stop_flag: Arc::new(AtomicBool::new(false)),
            handler: None,
            local_addr: None,
        }
    }

    pub fn start<F>(&mut self, stream_consumer: F) -> Result<(), Box<dyn std::error::Error>>
    where
        F: Fn(TcpStream),
        F: Send + Sync + 'static + Copy,
    {
        if let Some(_) = self.handler {
            return Err(Box::new(Error::new(
                ErrorKind::AlreadyExists,
                "Server is already started. ".to_string(),
            )));
        }
        let addr = self.addr.clone();
        log::info!("Start server at {:?}", addr);
        let fun = Arc::new(stream_consumer);
        self.stop_flag.store(false, Ordering::Relaxed);
        let flag = self.stop_flag.clone();

        let (addr_sender, addr_receiver) = mpsc::channel();
        let addr_sender = Arc::new(Mutex::new(addr_sender));

        let h = thread::Builder::new()
            .name(self.name.clone())
            .spawn(move || {
                if let Err(err) = Server::listen(addr.as_str(), fun, flag, addr_sender) {
                    log::info!("Error {:?} occurs when start listen to {:?}", err, addr);
                }
            })?;
        match addr_receiver.recv_timeout(Duration::from_secs(30)) {
            Ok(addr) => self.local_addr = Some(addr),
            Err(err) => log::warn!(
                "Cannot receive local address from the background thread. {:?}",
                err
            ),
        }
        self.handler = Some(h);
        Ok(())
    }

    fn listen<F>(
        addr: &str,
        stream_consumer: Arc<F>,
        flag: Arc<AtomicBool>,
        add_sender: Arc<Mutex<mpsc::Sender<String>>>,
    ) -> Result<(), Box<dyn std::error::Error>>
    where
        F: Fn(TcpStream),
        F: Send + Sync + 'static + Copy,
    {
        let listener = TcpListener::bind(addr)?;
        if let Ok(addr) = listener.local_addr() {
            match add_sender.lock() {
                Ok(sender) => {
                    if let Err(err) = sender.send(addr.to_string()){
                        log::error!("Error occures when send local address to main thread: {:?}", err);
                    }
                },
                Err(err) => log::warn!("Cannot get Local address sender to send local address to main thread, err: {:?}", err),
            }
        }
        let mut seq = 1u32;
        loop {
            if flag.load(Ordering::Relaxed) {
                break;
            }
            match listener.accept() {
                Ok((stream, addr)) => {
                    log::info!("receive request from address {:?}", addr);
                    stream.set_read_timeout(Some(Duration::from_secs(30)))?;
                    stream.set_write_timeout(Some(Duration::from_secs(30)))?;
                    let fun = Arc::clone(&stream_consumer);
                    thread::Builder::new()
                        .name("stream-handler".to_string() + "-" + seq.to_string().as_str())
                        .spawn(move || {
                            fun(stream);
                        })?;
                    seq += 1;
                }
                Err(err) => {
                    log::info!("Coldn't get client: {:?}", err);
                    continue;
                }
            }
        }
        Ok(())
    }

    pub fn shutdown(&mut self) {
        if let Some(handler) = self.handler.take() {
            self.stop_flag.store(true, Ordering::Relaxed);
            let mut send_local_addr = false;

            if let Some(local_addr) = self.local_addr.take() {
                if let Ok(_) = TcpStream::connect(local_addr) {
                    send_local_addr = true;
                }
            }

            if !send_local_addr {
                if let Err(_) = TcpStream::connect(self.addr.clone()) {}
            }
            if let Err(err) = handler.join() {
                log::info!("Join Server thread error {:?}", err);
            }
        }
    }
}

impl Drop for Server {
    fn drop(&mut self) {
        self.shutdown();
    }
}
